#![cfg(not(target_arch = "wasm32"))]

use std::i16;
use std::str;

use core::num::flt2dec::MAX_SIG_DIGITS;
use core::num::flt2dec::strategy::grisu::format_exact_opt;
use core::num::flt2dec::strategy::grisu::format_shortest_opt;
use core::num::flt2dec::{decode, DecodableFloat, FullDecoded, Decoded};

use rand::SeedableRng;
use rand::rngs::StdRng;
use rand::distributions::{Distribution, Uniform};

pub fn decode_finite<T: DecodableFloat>(v: T) -> Decoded {
    match decode(v).1 {
        FullDecoded::Finite(decoded) => decoded,
        full_decoded => panic!("expected finite, got {:?} instead", full_decoded)
    }
}


fn iterate<F, G, V>(func: &str, k: usize, n: usize, mut f: F, mut g: G, mut v: V) -> (usize, usize)
        where F: FnMut(&Decoded, &mut [u8]) -> Option<(usize, i16)>,
              G: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
              V: FnMut(usize) -> Decoded {
    assert!(k <= 1024);

    let mut npassed = 0; // f(x) = Some(g(x))
    let mut nignored = 0; // f(x) = None

    for i in 0..n {
        if (i & 0xfffff) == 0 {
            println!("in progress, {:x}/{:x} (ignored={} passed={} failed={})",
                     i, n, nignored, npassed, i - nignored - npassed);
        }

        let decoded = v(i);
        let mut buf1 = [0; 1024];
        if let Some((len1, e1)) = f(&decoded, &mut buf1[..k]) {
            let mut buf2 = [0; 1024];
            let (len2, e2) = g(&decoded, &mut buf2[..k]);
            if e1 == e2 && &buf1[..len1] == &buf2[..len2] {
                npassed += 1;
            } else {
                println!("equivalence test failed, {:x}/{:x}: {:?} f(i)={}e{} g(i)={}e{}",
                         i, n, decoded, str::from_utf8(&buf1[..len1]).unwrap(), e1,
                                        str::from_utf8(&buf2[..len2]).unwrap(), e2);
            }
        } else {
            nignored += 1;
        }
    }
    println!("{}({}): done, ignored={} passed={} failed={}",
             func, k, nignored, npassed, n - nignored - npassed);
    assert!(nignored + npassed == n,
            "{}({}): {} out of {} values returns an incorrect value!",
            func, k, n - nignored - npassed, n);
    (npassed, nignored)
}

pub fn f32_random_equivalence_test<F, G>(f: F, g: G, k: usize, n: usize)
        where F: FnMut(&Decoded, &mut [u8]) -> Option<(usize, i16)>,
              G: FnMut(&Decoded, &mut [u8]) -> (usize, i16) {
    if cfg!(target_os = "emscripten") {
        return // using rng pulls in i128 support, which doesn't work
    }
    let mut rng = StdRng::from_entropy();
    let f32_range = Uniform::new(0x0000_0001u32, 0x7f80_0000);
    iterate("f32_random_equivalence_test", k, n, f, g, |_| {
        let x = f32::from_bits(f32_range.sample(&mut rng));
        decode_finite(x)
    });
}

pub fn f64_random_equivalence_test<F, G>(f: F, g: G, k: usize, n: usize)
        where F: FnMut(&Decoded, &mut [u8]) -> Option<(usize, i16)>,
              G: FnMut(&Decoded, &mut [u8]) -> (usize, i16) {
    if cfg!(target_os = "emscripten") {
        return // using rng pulls in i128 support, which doesn't work
    }
    let mut rng = StdRng::from_entropy();
    let f64_range = Uniform::new(0x0000_0000_0000_0001u64, 0x7ff0_0000_0000_0000);
    iterate("f64_random_equivalence_test", k, n, f, g, |_| {
        let x = f64::from_bits(f64_range.sample(&mut rng));
        decode_finite(x)
    });
}

pub fn f32_exhaustive_equivalence_test<F, G>(f: F, g: G, k: usize)
        where F: FnMut(&Decoded, &mut [u8]) -> Option<(usize, i16)>,
              G: FnMut(&Decoded, &mut [u8]) -> (usize, i16) {
    // we have only 2^23 * (2^8 - 1) - 1 = 2,139,095,039 positive finite f32 values,
    // so why not simply testing all of them?
    //
    // this is of course very stressful (and thus should be behind an `#[ignore]` attribute),
    // but with `-C opt-level=3 -C lto` this only takes about an hour or so.

    // iterate from 0x0000_0001 to 0x7f7f_ffff, i.e., all finite ranges
    let (npassed, nignored) = iterate("f32_exhaustive_equivalence_test",
                                      k, 0x7f7f_ffff, f, g, |i: usize| {

        let x = f32::from_bits(i as u32 + 1);
        decode_finite(x)
    });
    assert_eq!((npassed, nignored), (2121451881, 17643158));
}

#[test]
fn shortest_random_equivalence_test() {
    use core::num::flt2dec::strategy::dragon::format_shortest as fallback;
    #[cfg(not(miri))] // Miri is too slow
    const N: usize = 10_000;
    #[cfg(miri)]
    const N: usize = 10;

    f64_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, N);
    f32_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, N);
}

#[test] #[ignore] // it is too expensive
fn shortest_f32_exhaustive_equivalence_test() {
    // it is hard to directly test the optimality of the output, but we can at least test if
    // two different algorithms agree to each other.
    //
    // this reports the progress and the number of f32 values returned `None`.
    // with `--nocapture` (and plenty of time and appropriate rustc flags), this should print:
    // `done, ignored=17643158 passed=2121451881 failed=0`.

    use core::num::flt2dec::strategy::dragon::format_shortest as fallback;
    f32_exhaustive_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS);
}

#[test] #[ignore] // it is too expensive
fn shortest_f64_hard_random_equivalence_test() {
    // this again probably has to use appropriate rustc flags.

    use core::num::flt2dec::strategy::dragon::format_shortest as fallback;
    f64_random_equivalence_test(format_shortest_opt, fallback,
                                         MAX_SIG_DIGITS, 100_000_000);
}

#[test]
fn exact_f32_random_equivalence_test() {
    use core::num::flt2dec::strategy::dragon::format_exact as fallback;
    #[cfg(not(miri))] // Miri is too slow
    const N: usize = 1_000;
    #[cfg(miri)]
    const N: usize = 3;

    for k in 1..21 {
        f32_random_equivalence_test(|d, buf| format_exact_opt(d, buf, i16::MIN),
                                             |d, buf| fallback(d, buf, i16::MIN), k, N);
    }
}

#[test]
fn exact_f64_random_equivalence_test() {
    use core::num::flt2dec::strategy::dragon::format_exact as fallback;
    #[cfg(not(miri))] // Miri is too slow
    const N: usize = 1_000;
    #[cfg(miri)]
    const N: usize = 3;

    for k in 1..21 {
        f64_random_equivalence_test(|d, buf| format_exact_opt(d, buf, i16::MIN),
                                             |d, buf| fallback(d, buf, i16::MIN), k, N);
    }
}
